package com.bitmain.mmst.feature.time;

import java.net.InetAddress;
import java.util.Map;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ntp.NTPUDPClient;
import org.apache.commons.net.ntp.TimeInfo;
import org.apache.commons.net.ntp.TimeStamp;

import com.bitmain.mmst.common.util.SpringContextHolder;
import com.bitmain.mmst.core.struct.ServiceResult;
import com.bitmain.mmst.domain.MineTimeZone;
import com.bitmain.mmst.domain.PreferenceGeneral;
import com.bitmain.mmst.web.service.PreferenceService;
import com.bitmain.ulnar.aider.DateAider;

/**
 * 
 *
 * @author Richard - rui.wu@bitmain.com
 * @since 2016年7月13日
 *
 */
public class TimeService {

	private static Map<Integer, MineTimeZone> timeZoneList= new TreeMap<Integer, MineTimeZone>();
	private static CopyOnWriteArrayList<String> serverList = new CopyOnWriteArrayList<String>();

	private static TimeZone timeZone;
	private static long timeMillis;
	private static long synchronizedTimestamp;
	
	private static Object timeLocker = new Object();
	private static Object zoneLocker = new Object();
	
	private static PreferenceService preferenceService = SpringContextHolder.getBean("preferenceService", PreferenceService.class);
	
	public void timeTest(){
		
		System.out.println("时间同步测试");
	}
	public void initialize(){
		initTimeZoneList();
		initServerList();
		initTimeZone();
		synchronize();
	}
	private void initTimeZoneList() {
		doPut(new MineTimeZone(TimeZone.getTimeZone("Etc/GMT+12"), "UTC-12:00(IDL-国际换日线)", "国际换日线"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Pacific/Midway"), "UTC-11:00(MIT-中途岛标准时间)", "中途岛标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("America/Adak"), "UTC-10:00(HST-夏威夷-阿留申标准时间)", "夏威夷-阿留申标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Pacific/Marquesas"), "UTC-09:30(MSIT-马克萨斯群岛标准时间)", "马克萨斯群岛标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("America/Anchorage"), "UTC-09:00(AKST-阿拉斯加标准时间)", "阿拉斯加标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Canada/Pacific"), "UTC-08:00(PSTA-太平洋标准时间A)", "太平洋标准时间A"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("America/Boise"), "UTC-07:00(MST-北美山区标准时间)", "北美山区标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("America/Bahia_Banderas"), "UTC-06:00(CST-北美中部标准时间)", "北美中部标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("America/Atikokan"), "UTC-05:00(EST-北美东部标准时间)", "北美东部标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("America/Caracas"), "UTC-04:30(RVT-委内瑞拉标准时间)", "委内瑞拉标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Canada/Atlantic"), "UTC-04:00(AST-大西洋标准时间)", "大西洋标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Canada/Newfoundland"), "UTC-03:30(NST-纽芬兰岛标准时间)", "纽芬兰岛标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("AGT"), "UTC-03:00(SAT-南美标准时间)", "南美标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Brazil/DeNoronha"), "UTC-02:00(BRT-巴西时间)", "巴西时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Atlantic/Cape_Verde"), "UTC-01:00(CVT-佛得角标准时间 )", "佛得角标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("UTC"), "UTC(WET-欧洲西部时区/GMT-格林威治标准时间)", "协调世界时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Europe/Amsterdam"), "UTC+01:00(CET-欧洲中部时区)", "中欧时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Europe/Athens"), "UTC+02:00(EET-欧洲东部时区)", "东欧时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Europe/Minsk"), "UTC+03:00(MSK-莫斯科时区)", "莫斯科标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Iran"), "UTC+03:30(IRT-伊朗标准时间)", "伊朗标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Yerevan"), "UTC+04:00(META-中东时区A)", "中东时间A"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Kabul"), "UTC+04:30(AFT-阿富汗标准时间)", "阿富汗标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Antarctica/Mawson"), "UTC+05:00(METB-中东时区B)", "中东时间B"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("IST"), "UTC+05:30(IDT-印度标准时间)", "印度标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Kathmandu"), "UTC+05:45(NPT-尼泊尔标准时间)", "尼泊尔标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Dacca"), "UTC+06:00(BHT-孟加拉标准时间)", "孟加拉标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Rangoon"), "UTC+06:30(MRT-缅甸标准时间)", "缅甸标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Bangkok"), "UTC+07:00(MST-中南半岛标准时间)", "中南半岛标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Shanghai"), "UTC+08:00(EAT-东亚标准时间/CST-中国标准时间)", "东亚标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Pyongyang"), "UTC+08:30(KRT-朝鲜标准时间)", "朝鲜标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Asia/Dili"), "UTC+09:00(FET-远东标准时间)", "远东标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Australia/Adelaide"), "UTC+09:30(ACST-澳大利亚中部标准时间)", "澳大利亚中部标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Australia/ACT"), "UTC+10:00(AEST-澳大利亚东部标准时间)", "澳大利亚东部标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Australia/LHI"), "UTC+10:30(FAST-澳大利亚远东标准时间)", "澳大利亚远东标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Pacific/Efate"), "UTC+11:00(VTT-瓦努阿图标准时间)", "瓦努阿图标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Pacific/Norfolk"), "UTC+11:30(NFT-诺福克岛标准时间)", "诺福克岛标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Pacific/Auckland"), "UTC+12:00(PSTB-太平洋标准时间B)", "太平洋标准时间B"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Pacific/Chatham"), "UTC+12:45(CIT-查塔姆群岛标准时间)", "查塔姆群岛标准时间"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Pacific/Apia"), "UTC+13:00(PSTC-太平洋标准时间C)", "太平洋标准时间C"));
		doPut(new MineTimeZone(TimeZone.getTimeZone("Pacific/Kiritimati"), "UTC+14:00(PSTD-太平洋标准时间D)", "太平洋标准时间D"));
	}

	private void doPut(MineTimeZone mtz) {
		timeZoneList.put(mtz.getOffset(), mtz);
	}
	private void initServerList() {
		// TODO k 查询数据库获取时间服务器列表，若查不到固定添加time-a ~ d
		serverList.add("time-a.nist.gov");
		serverList.add("time-b.nist.gov");
		serverList.add("time-c.nist.gov");
		serverList.add("time-d.nist.gov");
		serverList.add("nist1-macon.macon.ga.us");
		serverList.add("nist.netservicesgroup.com");
		serverList.add("nisttime.carsoncity.k12.mi.us");
		serverList.add("nist1-lnk.binary.net");
		serverList.add("wwv.nist.gov");
		serverList.add("time-a.timefreq.bldrdoc.gov");
		serverList.add("time-b.timefreq.bldrdoc.gov");
		serverList.add("time-c.timefreq.bldrdoc.gov");
		serverList.add("time.nist.gov");
		serverList.add("utcnist.colorado.edu");
		serverList.add("utcnist2.colorado.edu");
		serverList.add("ntp-nist.ldsbc.net");
		serverList.add("nist1-lv.ustiming.org");
		serverList.add("time-nw.nist.gov");
		serverList.add("nist-time-server.eoni.com");
		serverList.add("nist-time-server.eoni.com");
	}

	private void initTimeZone() {
		TimeZone tz = null;
		ServiceResult<PreferenceGeneral> rs = preferenceService.fetchGeneral();
		if(BooleanUtils.isTrue(rs.getSuccess())){
			PreferenceGeneral general = rs.getData();
			String timeZoneId = general.getTimeZoneID();
			if(StringUtils.isNoneBlank(timeZoneId)){
				tz = TimeZone.getTimeZone(timeZoneId);
			}
		}
		if(tz==null){
			tz = TimeZone.getDefault();
		}
		MineTimeZone mtz = timeZoneList.get(tz.getRawOffset());
		timeZone = TimeZone.getTimeZone(mtz.getId());
		System.out.println(timeZone.getID()+" "+timeZone.getRawOffset()+" "+timeZone.getDisplayName());
	}

	public void synchronize() {
		System.out.println("正在同步网络时间");
		NTPUDPClient timeClient = new NTPUDPClient();
		timeClient.setDefaultTimeout(1000);
		long netTime = -1;
		for(String server:serverList){
			System.out.println("从"+server+"服务器获取网络时间....");
			try {
				InetAddress inetAddress = InetAddress.getByName(server);
				TimeInfo timeInfo = timeClient.getTime(inetAddress);
				TimeStamp timeStamp = timeInfo.getMessage().getOriginateTimeStamp();
				netTime = timeStamp.getTime();
				if(netTime>0){
					break;
				}
			} catch (Exception e) {
			}
		}
		if(netTime<=0){
			netTime = System.currentTimeMillis();
			System.out.println("时间同步失败，启用系统时间"+DateAider.formatMillis(netTime, timeZone, "yyyy-MM-dd HH:mm:ss.SSS"));
		}else{
			System.out.println("时间同步成功"+DateAider.formatMillis(netTime, timeZone, "yyyy-MM-dd HH:mm:ss.SSS"));
		}
		setTimeMillis(netTime);
		timeClient.close();
	}

	private void setTimeMillis(long netTime) {
		synchronized(timeLocker){
			synchronizedTimestamp = System.currentTimeMillis();
			timeMillis = netTime;
		}
	}
	
	public static long getTimeMillis() {
		synchronized(zoneLocker){
			long balance = System.currentTimeMillis() - synchronizedTimestamp;
			return timeMillis + balance;
		}
	}
	public static TimeZone getTimeZone() {
		return timeZone;
	}
	public static void setTimeZone(TimeZone timeZone) {
		TimeService.timeZone = timeZone;
	}
	
	public static MineTimeZone getMineTimeZone() {
		return timeZoneList.get(getTimeZone().getRawOffset());
	}
}
